﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Data;
using Microsoft.Practices.RepositoryFactory.SchemaDiscovery;
using System.IO;
using Microsoft.Practices.RepositoryFactory.SchemaDiscovery.ObjectModel.Base;
using System.Data;
using System.Data.Common;
using System.Configuration;
using XSSAttack;

namespace ACorns.XSSAttack
{
	class Program
	{
		// Utility to try to inject lots of potentially dangerous scripts into a database
		// NOTE: The script never tries to add stuff, just update existing rows.
		// the script will go through all the schema and search for:
		// (n)varchar columns and text columns: injects complete or parts of the scripts from the scritps.txt file
		// int/bigint columns
		private static Random _random = new Random(Environment.TickCount);

		static void Main(string[] args)
		{
			Console.WriteLine("---------- Intro");
			Console.WriteLine("XSS/CSS Database Attack Tool - Simulates an XSS/CSS attack (http://tinyurl.com/owaspxss) on your database to see how well you website does output encoding.");
			Console.WriteLine("by Corneliu I. Tusnea, Microsoft MVP - Development Security");
			Console.WriteLine("http://www.acorns.com.au/ corneliu@acorns.com.au");
			Console.WriteLine();
			Console.WriteLine("Using Scripts from RSnake: http://ha.ckers.org/xss.html (with permission)");
			Console.WriteLine("I'm not responsible for any damage done to your website/database/business/fame before or after using the tool.");
			Console.WriteLine("USE THIS AT YOUR OWN RISK.");
			Console.WriteLine();

			// Check DB String
			string connectionString = ConfigurationManager.ConnectionStrings["Database"].ConnectionString;
			if ( String.IsNullOrEmpty(connectionString))
			{
				Console.WriteLine("Error: No connection string 'Database' specified in the config.");
				return;
			}

			Console.WriteLine("---------- Warning");
			Console.WriteLine("This tool will randomly modify string columns in your database and fill them with random XSS/CSS attacks.");
			Console.WriteLine("This should help you identify if your Web application is missing HTML encoding of elements in the UI.");
			Console.WriteLine("Please make sure you have a backup of your database before running this tool.");
			Console.WriteLine("As the update to your database might render your application useless.");

			Console.WriteLine();
			Console.WriteLine("->>>> Using Connection String: "); 
			Console.WriteLine(connectionString);
			Console.WriteLine();
			Console.WriteLine("Are you sure you want to modify this database? [Y]ES/[N]o");
			var key = Console.ReadKey();
			if ( key.Key != ConsoleKey.Y )
				return;
			Console.WriteLine();

			var ignoreList = ConfigurationManager.AppSettings["ignore"];
			if ( String.IsNullOrEmpty(ignoreList))
			{
				Console.WriteLine("Warning: There are no Schemas/Tables/Columns specified in the ignore list in the config file.");
				Console.WriteLine("Are you sure you want to inject XSS in all your database? [Y]es/[N]o");
				Console.WriteLine("You might render you application useless :)");

				key = Console.ReadKey();
				if (key.Key != ConsoleKey.Y)
					return;
				Console.WriteLine();
			}

			Console.WriteLine("---------- Executing");
			Dictionary<string, bool> ignoreElements = new Dictionary<string, bool>();
			if (!String.IsNullOrEmpty(ignoreList))
			{
				var split = ignoreList.Split(new[]
				                             	{
				                             		','
				                             	}, StringSplitOptions.RemoveEmptyEntries);
				foreach (var item in split)
				{
					ignoreElements[item.Trim().ToLowerInvariant()] = true;
				}
				Console.WriteLine("Ignoring " + ignoreElements.Count + " database elements: " + ignoreList);
			}

			//var lines = File.ReadAllLines("./Scripts.txt");
			Console.WriteLine();
			var attacks = XmlUtils.DeserializeXml<XssAttacks>(File.ReadAllText("./xssAttacks.xml"));
			attacks.AddRange(XmlUtils.DeserializeXml<XssAttacks>(File.ReadAllText("./cssAttacks.xml")));

			Console.WriteLine("Randomly modifying up to 5000 rows of data in each table using variations of " + attacks.Count + " attacks.");

			Console.WriteLine("Analyzing schema... (this might take several minutes)");

			int totalColumnUpdates = 0;
			int totalRowUpdates = 0;

			DbSchemaDiscoverer discoverer = new DbSchemaDiscoverer("Database");
			foreach (var table in discoverer.DiscoverTables())
			{
				// discover columns and see where we can inject stuff
				Console.Write(table.FullName);

				if (ignoreElements.ContainsKey(table.Schema.ToLowerInvariant()) || ignoreElements.ContainsKey(table.FullName.ToLowerInvariant()))
				{
					Console.WriteLine("\t - ignored");
					continue;
				}

				List<Column> primaryKeys = table.Columns.Where(c => c.IsPrimaryKey).ToList();
				if (primaryKeys.Count == 0)
				{
					Console.WriteLine("\t - ignored");
					continue;
				}

				Table table1 = table;
				List<Column> stringColumns = table.Columns.Where(
					c => 
						c.DbDataType.Type.Name == "String" && 
						!ignoreElements.ContainsKey((table1.FullName + "." + c.Name).ToLowerInvariant()) &&
						!ignoreElements.ContainsKey((".." + c.Name).ToLowerInvariant()) &&
						!c.IsForeignKey &&
						!c.IsUnique &&
						!c.IsPrimaryKey &&
						!c.IsReadOnly).ToList();

				if (stringColumns.Count == 0)
				{
					Console.WriteLine("\t - ignored (no string columns)");
					continue;
				}

				// Generate Update Statement
				// Read some data out of the table
				Console.Write(" \tReading");
				var readData = String.Format("SELECT TOP 5000 * FROM {0} ORDER BY NEWID()", table.FullName);

				StringBuilder updateQuery = new StringBuilder();
				updateQuery.AppendFormat("UPDATE {0} SET \r\n", table.FullName);
				int parameterCount = 0;
				for (int i = 0; i < stringColumns.Count; i++)
				{
					var col = stringColumns[i];

					if (i > 0)
						updateQuery.Append(", ");
					updateQuery.AppendFormat(" [{0}] = @p{1} \r\n", col.Name, parameterCount);
					parameterCount++;
				}
				updateQuery.AppendFormat(" WHERE ");
				for (int i = 0; i < primaryKeys.Count; i++)
				{
					var col = primaryKeys[i];
					if (i > 0)
						updateQuery.Append(" AND ");
					updateQuery.AppendFormat(" [{0}] = @p{1} ", col.Name, parameterCount);
					parameterCount++;
				}

				List<List<object>> primaryKeyValues = new List<List<object>>();

				Database database = DatabaseFactory.CreateDatabase("Database");
				using (var connection = database.CreateConnection())
				using (var readCommand = connection.CreateCommand())
				{
					connection.Open();
					readCommand.CommandText = readData;
					readCommand.CommandType = CommandType.Text;

					var reader = readCommand.ExecuteReader();
					while (reader.Read())
					{
						List<object> pkValues = new List<object>();
						foreach (var pkColumn in primaryKeys)
						{
							pkValues.Add(reader[pkColumn.Name]);
						}

						primaryKeyValues.Add(pkValues);
					}
				}
                
				foreach (var row in primaryKeyValues)
				{
					using (var updateConnection = database.CreateConnection())
					using (var command = updateConnection.CreateCommand())
					{
						updateConnection.Open();

						parameterCount = 0;
						// fill in the string columns with random values from the XSS scripts
						foreach (var stringColumn in stringColumns)
						{
							string value = attacks[_random.Next(attacks.Count)].Code;
							if (value.Length > stringColumn.DbDataType.Size)
								value = value.Substring(0, stringColumn.DbDataType.Size);

							totalColumnUpdates++;

							DbParameter param = command.CreateParameter();
							param.Direction = ParameterDirection.Input;
							param.ParameterName = "@p" + parameterCount;
							parameterCount++;
							param.Value = value;

							command.Parameters.Add(param);
						}

						// read the primary keys out
						for (int i = 0; i < primaryKeys.Count; i++)
						{
							var pkColumn = primaryKeys[i];
							DbParameter param = command.CreateParameter();
							param.Direction = ParameterDirection.Input;
							param.ParameterName = "@p" + parameterCount;
							parameterCount++;
							param.Value = row[i];

							command.Parameters.Add(param);
						}

						command.CommandText = updateQuery.ToString();
						command.CommandType = CommandType.Text;

						try
						{
							totalRowUpdates++;
							command.ExecuteNonQuery();
						}
						catch (Exception ex)
						{
							Console.WriteLine(ex.Message);
						}
					}
				}
				Console.WriteLine("\tUpdated " + primaryKeyValues.Count + " rows.");
			}

			Console.WriteLine();
			Console.WriteLine(totalColumnUpdates + " cells in " + totalRowUpdates + " rows were updated to contain potential XSS/CSS attacks.");
		}
	}
}
